/**
 * 严肃声明：
 * 开源版本请务必保留此注释头信息，若删除捷码开源〔GEMOS〕官方保留所有法律责任追究！
 * 本软件受国家版权局知识产权以及国家计算机软件著作权保护（登记号：2018SR503328）
 * 不得恶意分享产品源代码、二次转售等，违者必究。
 * Copyright (c) 2020 gemframework all rights reserved.
 * http://www.gemframework.com
 * 版权所有，侵权必究！
 */
package com.gemframework.common.aspect;
import com.alibaba.fastjson.JSON;
import com.gemframework.common.annotation.ApiLog;
import com.gemframework.common.exception.GemException;
import com.gemframework.common.utils.GemHttpUtils;
import com.gemframework.model.enums.LogStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Component  //声明组件
@Aspect //  声明切面
@ComponentScan  //组件自动扫描
@EnableAspectJAutoProxy //spring自动切换JDK动态代理和CGLIB
@Slf4j
public class LogAspect {

    /**
     * 在方法执行前进行切面
     */
    @Pointcut("(execution(* com.gemframework.modules..*.*(..))" +
            "&& (@annotation(org.springframework.web.bind.annotation.GetMapping)" +
            "||@annotation(org.springframework.web.bind.annotation.PutMapping)" +
            "||@annotation(org.springframework.web.bind.annotation.DeleteMapping)" +
            "||@annotation(org.springframework.web.bind.annotation.PostMapping)" +
            "||@annotation(org.springframework.web.bind.annotation.RequestMapping)))")
    public void log() {
    }


    /**
     * 程序执行之前
     */
    @Before("log()")
    public void before() {
        log.debug("程序执行前");
    }

    /**
     * 程序执行之后
     */
    @After("log()")
    public void after(){
        log.debug("程序执行后");
    }


    @Around("log()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object obj = null;
        GemException ex = null;
        StopWatch clock = null;
        Object[] args = null;
        ApiLog apiLog = null;
        boolean status = true;

        try {
            // 创建计时器
            clock = new StopWatch();
            // 启动计时器
            clock.start();
            // 获取ApiLog注解
            apiLog = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(ApiLog.class);
            // 获取参数
            args = point.getArgs();
            obj = point.proceed();
        } catch (GemException e) {
            status = false;
            ex = e;
            throw e;
        } catch (Throwable e) {
            status = false;
            log.error(e.getMessage());
            throw e;
        }finally {
            try {
                clock.stop();
            } catch (Throwable e2) {
                e2.printStackTrace();
                log.error(e2.getMessage());
            }
            if (apiLog != null) {
                writeLog(obj, apiLog, clock.getTime(), clock.getStartTime(), status, ex);
            }
        }
        return obj;
    }


    /**
     * @Title: apiLog
     * @Description: 日志记录
     * @param ret 响应对象
     * @param apiLog 注解对象
     * @param time 接口耗时
     * @param startTime 接口调用开始时间
     * @param status 操作状态
     */
    private void writeLog(Object ret, ApiLog apiLog, long time, long startTime, boolean status, GemException ex) {
        try {
            if (apiLog.console() || apiLog.db()) {
                HttpServletRequest request = GemHttpUtils.getHttpServletRequest();
                // 参数
                String params = JSON.toJSONString(request.getParameterMap());
                // 请求者ip地址
                String ip = request.getHeader("x-real-ip");
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("x-forwarded-for");
                }
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("Proxy-Client-IP");
                }
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("WL-Proxy-Client-IP");
                }
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
                // 接口名称
                String name = apiLog.name();
                // 请求报文
                String reqpack = "Params:" + params + "";
                // 响应报文
                String resppack = "";
                if (ex != null) {
                    resppack = JSON.toJSONString(ex);
                } else {
                    resppack = JSON.toJSONString(ret);
                }
                // 请求时间
                Date reqtime = new Date(startTime);
                // 响应时间
                Date resptime = new Date(startTime + time);
                LogStatus logStatus;
                if (status)
                    logStatus = LogStatus.SUCCESS;
                else
                    logStatus = LogStatus.FAIL;
                // 控制台输出日志
                if (apiLog.console()) {
                    StringBuffer sb = new StringBuffer();
                    sb.append("\r\n接口名称:【" + name + "】");
                    sb.append("\r\n请求报文:IP:【" + ip + "】," + reqpack);
                    sb.append("\r\n响应报文:" + resppack);
                    sb.append("\r\n请求时间:" + DateFormatUtils.format(reqtime, "yyyy-MM-dd HH:mm:ss.SSS"));
                    sb.append("\r\n响应时间:" + DateFormatUtils.format(resptime, "yyyy-MM-dd HH:mm:ss.SSS"));
                    sb.append("\r\n接口耗时:" + time + "毫秒");
                    sb.append("\r\n接口调用:" + logStatus.getMsg());
                    log.info(sb.toString());
                }
                //日志记录入库
                if (apiLog.db()) {
                    //TODO:持久化
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }
}